package org.budo.dubbo.protocol.http.exception.filter;

import java.lang.reflect.Method;

import org.budo.dubbo.protocol.http.exception.remoting.BudoDataAccessException;
import org.budo.dubbo.protocol.http.exception.remoting.ProviderSideRuntimeException;
import org.budo.support.lang.util.ReflectUtil;

import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.ReflectUtils;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.RpcResult;
import com.alibaba.dubbo.rpc.service.GenericService;

/**
 * 替换默认的ExceptionFilter,减少异常日志
 * 
 * @author lmw
 * @see com.alibaba.dubbo.rpc.filter.ExceptionFilter
 * @see org.budo.dubbo.protocol.http.exception.filter.ProviderSideNoLogException
 */
public class BudoExceptionFilter implements Filter {
    private static final boolean HAS_BUDO_BUSINESS_EXCEPTION = ReflectUtil.hasClass("org.budo.support.exception.business.BusinessException");

    private static final boolean HAS_SPRING_DATA_ACCESS_EXCEPTION = ReflectUtil.hasClass("org.springframework.dao.DataAccessException");

    private final Logger log;

    public BudoExceptionFilter() {
        this(LoggerFactory.getLogger(BudoExceptionFilter.class));
    }

    public BudoExceptionFilter(Logger logger) {
        this.log = logger;
    }

    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        try {
            Result result = invoker.invoke(invocation);
            if (result.hasException() && GenericService.class != invoker.getInterface()) {
                try {
                    return this.parseResultException(invoker, invocation, result);
                } catch (Throwable e) {
                    log.warn("#50 Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() //
                            + ", service: " + invoker.getInterface().getName() //
                            + ", method: " + invocation.getMethodName() //
                            + ", exception: " + e.getClass().getName() //
                            + ", message: " + e.getMessage(), e);
                    return result;
                }
            }

            return result;
        } catch (RuntimeException e) {
            if (("" + e).contains("Failed to validate service")) {
                if (log.isDebugEnabled()) { // debug 打 validate 错误
                    log.debug("#63 Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() //
                            + ", service: " + invoker.getInterface().getName() //
                            + ", method: " + invocation.getMethodName() //
                            + ", exception: " + e.getClass().getName() //
                            + ", message:" + e.getMessage(), e);
                }
            } else {
                log.error("#70 Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() //
                        + ", service: " + invoker.getInterface().getName() //
                        + ", method: " + invocation.getMethodName() //
                        + ", exception: " + e.getClass().getName() //
                        + ", message: " + e.getMessage(), e);
            }
            throw e;
        }
    }

    private Result parseResultException(Invoker<?> invoker, Invocation invocation, Result result) {
        Throwable resultException = result.getException();

        // ServerSideNoLogException，直接抛出
        if (resultException instanceof ProviderSideNoLogException) {
            return result;
        }

        // Assert异常不打日志，直接抛出给客户端
        String exceptionTypeName = resultException.getClass().getName();
        if (exceptionTypeName.startsWith("org.budo.support.assertion.")) {
            return result;
        }

        // validation 失败
        if (exceptionTypeName.startsWith("org.budo.validation.exception.")) {
            return result;
        }

        // 数据库key重复，原样返回到调用方
        if (exceptionTypeName.endsWith("DuplicateKeyException") || exceptionTypeName.endsWith("MySQLIntegrityConstraintViolationException")) {
            return result;
        }

        // 业务异常
        if (HAS_BUDO_BUSINESS_EXCEPTION && resultException instanceof org.budo.support.exception.business.BusinessException) {
            return result;
        }

        // spring 数据库访问异常，包装成BudoDataAccessException，返回
        if (HAS_SPRING_DATA_ACCESS_EXCEPTION && resultException instanceof org.springframework.dao.DataAccessException) {
            log.error("#111 spring DataAccessException " + resultException, resultException);

            String message = "#113 url=" + invoker.getUrl().toServiceString() + ", error=" + resultException;
            return new RpcResult(new BudoDataAccessException(message));
        }

        // 如果是checked异常，直接抛出
        if (!(resultException instanceof RuntimeException) && (resultException instanceof Exception)) {
            return result;
        }

        // 在方法签名上有声明，直接抛出
        try {
            Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
            Class<?>[] exceptionClassses = method.getExceptionTypes();
            for (Class<?> exceptionClass : exceptionClassses) {
                if (resultException.getClass().equals(exceptionClass)) {
                    return result;
                }
            }
        } catch (NoSuchMethodException e) {
            return result;
        }

        // 未在方法签名上定义的异常，在服务器端打印ERROR日志
        log.error("#134 Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() //
                + ", service: " + invoker.getInterface().getName() //
                + ", method: " + invocation.getMethodName() //
                + ", exception: " + resultException.getClass().getName() //
                + ", message: " + resultException.getMessage(), resultException);

        // 异常类和接口类在同一jar包里，直接抛出
        String serviceCodeBase = ReflectUtils.getCodeBase(invoker.getInterface());
        String exceptionCodeBase = ReflectUtils.getCodeBase(resultException.getClass());

        if (serviceCodeBase == null || exceptionCodeBase == null || serviceCodeBase.equals(exceptionCodeBase)) {
            return result;
        }

        // 是JDK自带的异常，直接抛出
        if (exceptionTypeName.startsWith("java.") || exceptionTypeName.startsWith("javax.")) {
            return result;
        }

        // 是budo的异常，直接抛出
        if (exceptionTypeName.startsWith("org.budo.")) {
            return result;
        }

        // 是Dubbo本身的异常，直接抛出
        if (resultException instanceof RpcException) {
            return result;
        }

        // 其他异常，包装成ProviderSideRuntimeException抛给客户端
        String message = "#164 error=" + resultException + ",  url=" + invoker.getUrl().toServiceString();

        ProviderSideRuntimeException providerSideRuntimeException = new ProviderSideRuntimeException(message);
        return new RpcResult(providerSideRuntimeException);
    }
}